package com.apes.framework.jpa.generator.service;

import com.apes.framework.api.DomainService;
import com.apes.framework.jpa.ApesBean;
import com.apes.framework.jpa.Connector;
import com.apes.framework.jpa.generator.ApesGenerator;
import com.apes.framework.jpa.generator.GeneratorUtil;
import com.apes.framework.jpa.generator.model.Sequence;
import com.apes.framework.jpa.generator.model.SequenceDateRange;
import com.apes.framework.jpa.generator.repository.SequenceDateRangeRepository;
import com.apes.framework.jpa.generator.repository.SequenceRepository;
import com.apes.framework.plugin.esb.api.SimpleRequest;
import com.apes.framework.util.DateUtil;
import com.apes.framework.util.JpaUtil;
import com.apes.framework.util.ReflectUtil;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.Subscribe;
import lombok.Data;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 功能：Sequence 服务
 *
 * @author xul
 * @create 2018-03-13 19:13
 */
@Service
public class SequenceService extends DomainService {
    protected final Log logger = LogFactory.getLog(this.getClass());

    private final static Map<String, String> dateFormat = new HashMap() {{
        put("year", "yyyy");  //4位数字表示的当前年份：%(year)
        put("yy", "yy");  //两位数的当前年份：%(yy)
        put("month", "MM");  //月：%(month)
        put("day", "dd");  //日：%(day)
        put("h24", "HH");  //小时 00->24: %(h24)
        put("h12", "hh");  //小时 00->12: %(h12)
        put("min", "mm");  //分钟: %(min)
        put("sec", "ss");  //秒：%(sec)
    }};

    @Autowired
    private SequenceRepository sequenceRepository;

    @Autowired
    private SequenceDateRangeRepository dateRangeRepository;

    @Autowired
    private SequenceManager sequenceManager;

    private AsyncEventBus eventBus;

    public SequenceService(AsyncEventBus eventBus) {
        this.eventBus = eventBus;
        this.eventBus.register(this);
    }

    private synchronized Sequence createSequence(Sequence sequence) {
        Sequence seq = sequenceManager.getSequence(sequence.getCode());
        if (seq != null) return seq;
        setId(sequence);
        seq = sequenceRepository.saveAndFlush(sequence);
        if (sequenceManager.isSupportsSequences(sequence) && !sequence.isUseDateRange()) {
            createSequence(getSequenceName(sequence), sequence.getNumberIncrement(), sequence.getNumberNext());
        }
        return seq;
    }

    /**
     * 功能：通过Sequence获取Id，如果不存在，则创建
     */
    public String getId(SimpleRequest request) {
        Sequence sequence = request.getO(Sequence.class);
        return getId(sequence);
    }

    /**
     * 功能：通过sequenceCode获取Id，如果不存在，则创建
     */
    public String getId(String sequenceCode) {
        Vo vo = nextByCode(sequenceCode);
        return getNextChar(vo);
    }

    /**
     * 功能：通过Sequence获取Id，如果不存在，则创建
     */
    private String getId(Sequence sequence) {
        Vo vo = nextByCode(sequence.getCode());
        return getNextChar(vo);
    }

    public Vo nextByCode(String sequenceCode) {
        Sequence sequence = sequenceManager.getSequence(sequenceCode);
        if (sequence == null) {
            eventBus.post(new CreateSequenceEvent(GeneratorUtil.getSequence(sequenceCode)));
            int i = 0;
            while (i < 10 && sequence == null) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e1) {
                }
                sequence = sequenceManager.getSequence(sequenceCode);
                i++;
            }
        }
        if (sequence == null) throw new RuntimeException("序列：'" + sequenceCode + "' 未创建！");
        return next(sequence);
    }

    private SequenceDateRange getSequenceDateRange(Sequence sequence) {
        SequenceDateRange sequenceDateRange = sequenceManager.getSequenceDateRange(sequence);
        if (sequenceDateRange == null) {
            eventBus.post(new CreateSequenceDateRangeEvent(sequence));
            int i = 0;
            while (i < 10 && sequence == null) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e1) {
                }
                sequenceDateRange = sequenceManager.getSequenceDateRange(sequence);
                i++;
            }
            if (sequenceDateRange == null) throw new RuntimeException("序列：'" + sequence.getCode() + "' 未创建！");
        }
        return sequenceDateRange;
    }

    private Vo next(Sequence sequence) {
        if (!sequence.isUseDateRange()) {
            return nextDo(sequence);
        }
        /** 日期模式 **/
        SequenceDateRange sequenceDateRange = getSequenceDateRange(sequence);
        return nextDo(sequenceDateRange);
    }

    private Vo nextDo(Sequence sequence) {
        long id;
        if (sequenceManager.isSupportsSequences(sequence)) {
            String sequenceName = getSequenceName(sequence);
            id = selectNextVal(sequenceName);
        } else {
            id = updateNoGap(sequence);
        }
        return getNextChar(sequence, id);
    }

    private Vo nextDo(SequenceDateRange sequenceDateRange) {
        long id;
        if (sequenceManager.isSupportsSequences(sequenceDateRange.getSequence())) {
            id = selectNextVal(getSequenceName(sequenceDateRange));
        } else {
            id = updateNoGap(sequenceDateRange);
        }
        return getNextChar(sequenceDateRange.getSequence(), id);
    }

    private String getSequenceName(Sequence sequence) {
        return String.format("ir_sequence_%05d", sequence.getId());
    }

    private static String replace(String text) {
        Iterator entries = dateFormat.entrySet().iterator();
        while (entries.hasNext()) {
            Map.Entry<String, String> entry = (Map.Entry) entries.next();
            String key = entry.getKey();
            String value = entry.getValue();
            text = replace(text, key, value, new Date());
        }
        return text;
    }

    private static String replace(String text, String key, String value, Date date) {
        String regex = "(%\\()(" + key + ")(\\))";
        return text.replaceAll(regex, DateUtil.format(date, value));
    }

    private String getNextChar(Vo vo) {
        String prefix = vo.getPrefix();
        if (prefix != null) {
            prefix = replace(prefix);
        }
        String suffix = vo.getSuffix();
        if (suffix != null) {
            suffix = replace(suffix);
        }
        prefix = prefix == null ? "" : prefix;
        suffix = suffix == null ? "" : suffix;
        return prefix + String.format("%0" + vo.getPadding() + "d", vo.getNumberNext()) + suffix;
    }

    private Vo getNextChar(Sequence sequence, long numberNext) {
        return new Vo(sequence.getPrefix(), sequence.getSuffix(), sequence.getPadding(), numberNext);
    }

    private long updateNoGap(Sequence sequence) {
        long numberNext = sequence.getNumberNext();
        sequence.addNumberNext();
        sequenceRepository.save(sequence);
        return numberNext;
    }

    private long updateNoGap(SequenceDateRange sequenceDateRange) {
        long numberNext = sequenceDateRange.getNumberNext();
        sequenceDateRange.addNumberNext();
        dateRangeRepository.save(sequenceDateRange);
        return numberNext;
    }

    @Autowired
    private Connector connector;

    private long selectNextVal(String sequenceName) {
        String sql = sequenceManager.getDialect().getSequenceNextValString(sequenceName);
        Connection conn;
        PreparedStatement stmt = null;
        ResultSet resultSet = null;
        try {
            conn = connector.getConnection();
            stmt = conn.prepareStatement(sql);
            resultSet = stmt.executeQuery();
            while (resultSet.next()) {
                return resultSet.getLong("NEXTVAL");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (null != resultSet) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (null != stmt) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        throw new RuntimeException("Sequence取值异常！");
    }

    /**
     * 创建DateRangeSequence
     */
    public synchronized void createDateRangeSeq(Sequence sequence) {
        SequenceDateRange sequenceDateRange = sequenceManager.getSequenceDateRange(sequence);
        if (sequenceDateRange != null) return;
        Date date = new Date();
        String year = DateUtil.format(date, "yyyy");
        String dateFrom = String.format("%s-01-01", year);
        String dateTo = String.format("%s-12-31", year);
        SequenceDateRange dateRange = dateRangeRepository.findFirstBySequenceAndDateFromGreaterThanEqualAndDateFromLessThanEqualOrderByDateFromDesc(sequence, date, DateUtil.parse(dateTo));
        if (dateRange != null) {
            dateTo = DateUtil.format(DateUtil.add(dateRange.getDateFrom(), Calendar.DATE, -1), DateUtil.SHOW_DATE_FORMAT);
        }
        dateRange = dateRangeRepository.findFirstBySequenceAndDateToGreaterThanEqualAndDateToLessThanEqualOrderByDateToDesc(sequence, DateUtil.parse(dateFrom), date);
        if (dateRange != null) {
            dateFrom = DateUtil.format(DateUtil.add(dateRange.getDateTo(), Calendar.DATE, 1), DateUtil.SHOW_DATE_FORMAT);
        }
        dateRange = new SequenceDateRange();
        dateRange.setSequence(sequence);
        dateRange.setDateFrom(DateUtil.parse(dateFrom));
        dateRange.setDateTo(DateUtil.parse(dateTo));
        saveAndFlush(dateRange);
    }

    private SequenceDateRange saveAndFlush(SequenceDateRange sequenceDateRange) {
        setId(sequenceDateRange);
        sequenceDateRange = dateRangeRepository.saveAndFlush(sequenceDateRange);
        if (sequenceManager.isSupportsSequences(sequenceDateRange.getSequence())) {
            createSequence(getSequenceName(sequenceDateRange), sequenceDateRange.getSequence().getNumberIncrement(), sequenceDateRange.getNumberNext());
        }
        return sequenceDateRange;
    }

    private String getSequenceName(SequenceDateRange sequenceDateRange) {
        return String.format("ir_sequence_%05d_%03d", sequenceDateRange.getSequence().getId(), sequenceDateRange.getId());
    }

    /**
     * 功能：创建 Sequence
     */
    private void createSequence(String sequenceName, int numberIncrement, int numberNext) {
        String[] strings = sequenceManager.getDialect().getCreateSequenceStrings(sequenceName, numberNext, numberIncrement);
        for (String sql : strings) {
            sequenceRepository.exec(sql);
        }
    }

    /**
     * 功能：根据ApesGenerator注解产生值
     */
    public void setId(Object entity) {
        if (entity == null) return;
        if (!ApesBean.class.isAssignableFrom(entity.getClass())) {
            logger.error("实体需要继承ApesBean类！");
            return;
        }
        if (ReflectUtil.getAnnotation(entity.getClass(), Entity.class) == null) {
            logger.error("实体需要注解：@Entity ！");
            return;
        }

        List<PropertyDescriptor> descriptors = getDescriptors(entity);
        for (PropertyDescriptor descriptor : descriptors) {
            Class clazz = descriptor.getPropertyType();
            String fieldName = descriptor.getName();
            if (Collection.class.isAssignableFrom(clazz)) {
                Object value = ReflectUtil.getFieldValue(entity, descriptor);
                if (value == null) continue;
                if (((Collection) value).isEmpty()) continue;
                for (Object o : (Collection) value) {
                    setId(o);
                }
            } else if (ApesBean.class.isAssignableFrom(clazz)) {
                Object value = ReflectUtil.getFieldValue(entity, descriptor);
                if (value == null) continue;
                setId(value);
            } else {
                /** 检查Id值是否为空 */
                if (!isNull(ReflectUtil.getFieldValue(entity, descriptor))) continue;
                ApesGenerator apesGenerator = ReflectUtil.getAnnotation(entity, fieldName, ApesGenerator.class);
                if (apesGenerator == null) continue;
                Object value;
                Vo vo = nextByCode(apesGenerator.code());
                if (String.class.isAssignableFrom(clazz)) {
                    String id = getNextChar(vo);
                    id = replace(entity, id);
                    value = id;
                } else if (Long.class.isAssignableFrom(clazz)) {
                    value = Long.valueOf(vo.numberNext);
                } else if (long.class.isAssignableFrom(clazz)) {
                    value = vo.numberNext;
                } else if (Integer.class.isAssignableFrom(clazz)) {
                    value = Integer.valueOf(Long.valueOf(vo.numberNext).intValue());
                } else if (int.class.isAssignableFrom(clazz)) {
                    value = Long.valueOf(vo.numberNext).intValue();
                } else {
                    throw new RuntimeException("实体Id类型定义错误！");
                }
                try {
                    Method writer = descriptor.getWriteMethod();
                    writer.invoke(entity, value);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    private static Cache<String, List<PropertyDescriptor>> propertyDescriptorCache = CacheBuilder.newBuilder()
            .maximumSize(20000)  /**设置缓存的最大容量**/
            .expireAfterAccess(60, TimeUnit.MINUTES)  /**设置缓存项在60分钟内没有被访问，则该缓存项会失效**/
            .build();

    private List<PropertyDescriptor> getDescriptors(Object entity) {
        String className = ReflectUtil.getClass(entity).getName();
        List<PropertyDescriptor> descriptors;
        if (propertyDescriptorCache.asMap().containsKey(className)) {
            descriptors = propertyDescriptorCache.getIfPresent(className);
        } else {
            PropertyDescriptor[] propertyDescriptors = ReflectUtil.getPropertyDescriptors(entity);
            descriptors = Arrays.stream(propertyDescriptors).filter(descriptor -> {
                Class clazz = descriptor.getPropertyType();
                if (Collection.class.isAssignableFrom(clazz)) {
                    OneToMany oneToMany = ReflectUtil.getAnnotation(entity, descriptor.getName(), OneToMany.class);
                    if (oneToMany != null && oneToMany.cascade().length > 0) {
                        return true;
                    }
                } else if (ApesBean.class.isAssignableFrom(clazz)) {
                    OneToOne oneToOne = ReflectUtil.getAnnotation(entity, descriptor.getName(), OneToOne.class);
                    if (oneToOne != null && oneToOne.cascade().length > 0) {
                        return true;
                    }
                } else if (ReflectUtil.isPrimitive(clazz)) {
                    Id id = ReflectUtil.getAnnotation(entity, descriptor.getName(), Id.class);
                    if (id != null) {
                        return true;
                    }
                }
                return false;
            }).collect(Collectors.toList());
            propertyDescriptorCache.put(className, descriptors);
        }
        return descriptors;
    }

    private boolean isNull(Object o) {
        if (o == null) return true;
        String s = String.valueOf(o);
        return s.equals("") || s.equals("0") || s.equals("null");
    }

    /**
     * 功能：替换实体中的变量
     */
    private String replace(Object entity, String text) {
        Matcher matcher = Pattern.compile("(%\\()(.+?)(\\))").matcher(text);
        if (matcher.find()) {
            String key = matcher.group(2).trim();
            PropertyDescriptor descriptor = ReflectUtil.getFieldPropertyDescriptor(entity, key);
            Object value = null;
            if (descriptor != null) {
                try {
                    Method getter = descriptor.getReadMethod();
                    value = getter != null ? getter.invoke(entity) : null;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (value instanceof ApesBean) {
                String partyClassName = "com.apes.scm.masterdata.party.model.Party";
                if (value.getClass().getName().equals(partyClassName) && key.equals("company")) {
                    try {
                        Method method = Class.forName(partyClassName).getMethod("getRoleCode", String.class);
                        value = method.invoke(value, "Company");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else {
                    value = JpaUtil.getId((ApesBean) value);
                }
            }
            if (!ReflectUtil.isPrimitive(value.getClass())) value = null;
            text = text.replaceAll("(%\\()(" + key + ")(\\))", value == null ? "" : String.valueOf(value));
            text = replace(entity, text);
        }
        return text;
    }

    @Data
    class Vo {
        /**
         * 前缀
         **/
        private String prefix;

        /**
         * 后缀
         **/
        private String suffix;

        /**
         * 序列大小
         **/
        private int padding;

        /**
         * 下一号码
         **/
        long numberNext;

        public Vo(String prefix, String suffix, int padding, long numberNext) {
            this.prefix = prefix;
            this.suffix = suffix;
            this.padding = padding;
            this.numberNext = numberNext;
        }
    }

    @Subscribe
    public void createSequence(CreateSequenceEvent event) {
        execute(() -> {
            Sequence sequence = createSequence(event.getSequence());
            if (sequence.isUseDateRange()) createDateRangeSeq(sequence);
        });
    }

    @Data
    public class CreateSequenceEvent {

        public CreateSequenceEvent(Sequence sequence) {
            this.sequence = sequence;
        }

        private Sequence sequence;
    }

    @Subscribe
    public void createDateRangeSequence(CreateSequenceDateRangeEvent event) {
        execute(() -> createDateRangeSeq(event.getSequence()));
    }

    @Data
    public class CreateSequenceDateRangeEvent {

        public CreateSequenceDateRangeEvent(Sequence sequence) {
            this.sequence = sequence;
        }

        private Sequence sequence;
    }


    @Autowired
    private TransactionTemplate transactionTemplate;

    public void execute(Callback callback) {
        transactionTemplate.execute(status -> {
            try {
                callback.execute();
                return null;
            } catch (Exception e) {
                status.isRollbackOnly();
                throw e;
            }
        });
    }

    @FunctionalInterface
    public interface Callback {
        @Nullable
        void execute();
    }

}
